package org.geektimes.config.microprofile;

import cn.hutool.core.util.StrUtil;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigValue;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.Converter;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static java.util.stream.StreamSupport.stream;
import static org.apache.commons.lang.ClassUtils.wrapperToPrimitive;

/**
 * @FileName: JavaConfig
 * @Description: TODO
 * @Author: MaWenHao
 * @Created: 2021/3/17 - 下午10:09
 */
public class DefaultConfig implements Config {

    private List<ConfigSource> configSources;

    private Map<String, Converter> converterMap;

    private Map<String, String> cacheConfig = new ConcurrentHashMap<>();

    public DefaultConfig() {
        init();
    }

    @Override
    public <T> T getValue(String s, Class<T> aClass) {
        String value = cacheConfig.get(s);
        if (StrUtil.isEmpty(value)) {
            value = getValueFromSource(s);
        }
        if (StrUtil.isNotEmpty(value)) {
            Class wrapperType = wrapperToPrimitive(aClass);
            if (wrapperType == null) {
                wrapperType = aClass;
            }
            Converter<T> converter = converterMap.get(wrapperType.getTypeName());
            return converter.convert(value);
        }
        return null;
    }

    private String getValueFromSource(String s) {
        for (ConfigSource configSource : configSources) {
            String value = configSource.getValue(s);
            if (StrUtil.isNotEmpty(value)) {
                cacheConfig.put(s, value);
                return value;
            }
        }
        return null;
    }

    @Override
    public ConfigValue getConfigValue(String propertyName) {
        return null;
    }

    @Override
    public <T> Optional<T> getOptionalValue(String s, Class<T> aClass) {
        return Optional.ofNullable(getValue(s, aClass));
    }

    @Override
    public Iterable<String> getPropertyNames() {
        return stream(configSources.spliterator(), false)
                .map(ConfigSource::getPropertyNames)
                .collect(LinkedHashSet::new, Set::addAll, Set::addAll);
    }

    @Override
    public Iterable<ConfigSource> getConfigSources() {
        return Collections.unmodifiableList(configSources);
    }

    @Override
    public <T> Optional<Converter<T>> getConverter(Class<T> aClass) {
        return Optional.ofNullable(converterMap.get(aClass.getTypeName()));
    }

    @Override
    public <T> T unwrap(Class<T> aClass) {
        return null;
    }

    public void refresh() {
        init();
        cacheConfig.clear();
    }


    private void init() {
        List<ConfigSource> configSources = new LinkedList<>();
        ServiceLoader<ConfigSource> serviceLoader = ServiceLoader.load(ConfigSource.class);
        serviceLoader.forEach(configSources::add);
        configSources.sort(Comparator.comparingInt(ConfigSource::getOrdinal));
        this.configSources = configSources;

        Map<String, Converter> converterMap = new HashMap<>();
        ServiceLoader<Converter> converterServiceLoader = ServiceLoader.load(Converter.class);
        converterServiceLoader.forEach(c -> initConverter(c, converterMap));
        this.converterMap = converterMap;
    }

    private void initConverter(Converter converter, Map<String, Converter> converterMap) {
        Type[] genericInterfaces = converter.getClass().getGenericInterfaces();
        Optional<Type> first = Arrays.stream(genericInterfaces)
                .filter(c -> c instanceof ParameterizedType)
                .map(ParameterizedType.class::cast)
                .filter(c -> Objects.equals(c.getRawType().getTypeName(), Converter.class.getTypeName()))
                .map(ParameterizedType::getActualTypeArguments)
                .flatMap(Arrays::stream)
                .findFirst();
        if (first.isPresent()) {
            converterMap.put(first.get().getTypeName(), converter);
        }
    }

    /**
     * 转换属性值（如果需要）
     *
     * @param propertyValue
     * @return
     */
    protected String transformPropertyValue(String propertyValue) {
        return propertyValue;
    }

}
